#!/usr/bin/python3

"""
Spawn a command, and terminate it with a backtrace after a timeout.

Usage:
    py-watch TIMEOUT command args
"""

from __future__ import absolute_import
from __future__ import print_function

import os
import sys
import signal
import threading

import six

if six.PY2:
    import subprocess32 as subprocess
else:
    import subprocess


class Terminated(Exception):

    def __init__(self, signo):
        self.signo = signo


def parse_args(argv):
    return float(argv[1]), argv[2:]


def dump_trace(proc):
    subprocess.call([
        'gdb', '-p', str(proc.pid), '--batch', '-ex',
        'thread apply all py-bt'])


def terminate(signo, frame):
    raise Terminated(signo)


def run_in_new_session(argv):
    return subprocess.Popen(['setsid'] + argv)


def kill_proc_session(proc):
    os.killpg(proc.pid, signal.SIGTERM)
    try:
        proc.wait(2)
    except subprocess.TimeoutExpired:
        os.killpg(proc.pid, signal.SIGKILL)
        return 128 + signal.SIGKILL
    else:
        return 128 + signal.SIGTERM


if __name__ == '__main__':
    signal.signal(signal.SIGINT, terminate)
    signal.signal(signal.SIGTERM, terminate)

    timeout, watched_argv = parse_args(sys.argv)
    watched_proc = run_in_new_session(watched_argv)
    try:
        rc = watched_proc.wait(timeout=timeout)
    except subprocess.TimeoutExpired:
        print("=============================================================\n"
              "=                 Watched process timed out                 =\n"
              "=============================================================")
        sys.stdout.flush()
        dump_trace(watched_proc)
        print("=============================================================\n"
              "=                Terminating watched process                =\n"
              "=============================================================")
        sys.stdout.flush()
        sys.exit(kill_proc_session(watched_proc))
    except Terminated as e:
        print("py-watch: Terminated by signal {}".format(e.signo))
        kill_proc_session(watched_proc)
        sys.exit(128 + e.signo)
    else:
        sys.exit(rc)
